import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import { MagicApiClient, MagicServerConfig } from './magicApiClient';
import { MAGIC_RESOURCE_TYPES, MagicResourceType } from './types';
import { MirrorMetaManager } from './mirror/metaManager';
import { MirrorFileUtils } from './mirror/fileUtils';
import { MirrorPreferenceManager } from './mirror/preferenceManager';
import { MirrorWorkspaceUtils } from './mirror/workspaceUtils';
import { MirrorSyncManager } from './mirror/syncManager';
import { MirrorMergeManager } from './mirror/mergeManager';
import { MIRROR_FILE_CONSTANTS, MirrorMeta, MirrorFileMeta } from './mirror/types';
import { addFileError, clearFileErrors } from './diagnostics';
import { MirrorMetaLinkageManager } from './mirror/metaLinkageManager';


/**
 * 镜像工作区管理器
 * 用户需求：统一管理镜像工作区的各种操作，协调文件管理、同步、合并和事件处理模块
 */
export class MirrorWorkspaceManager {
    private static instance: MirrorWorkspaceManager;
    private context: vscode.ExtensionContext;

    // 模块化管理器
    private metaManager: MirrorMetaManager;
    private syncManager: MirrorSyncManager;
    private mergeManager: MirrorMergeManager;
    private fileUtils: MirrorFileUtils;
    private preferenceManager: MirrorPreferenceManager;
    private workspaceUtils: MirrorWorkspaceUtils;
    private linkageManager: MirrorMetaLinkageManager;

    // 记录每个镜像根目录的活动监听器，支持断开连接
    private activeMirrorDisposables: Map<string, vscode.Disposable[]> = new Map();
    // 每个镜像根的“一次性保存拦截”键集合
    private suppressNextSavePushKeys: Map<string, Set<string>> = new Map();

    private constructor(context: vscode.ExtensionContext) {
        this.context = context;

        // 初始化模块化管理器
        this.metaManager = new MirrorMetaManager();
        this.syncManager = new MirrorSyncManager();
        this.mergeManager = new MirrorMergeManager();
        this.fileUtils = new MirrorFileUtils();
        this.preferenceManager = new MirrorPreferenceManager(context);
        this.workspaceUtils = new MirrorWorkspaceUtils();
        this.linkageManager = new MirrorMetaLinkageManager();

        // 注入依赖：让 syncManager 使用 metaManager 的实现
        this.syncManager.setDependencies(
            this.metaManager.readMirrorMeta.bind(this.metaManager),
            this.metaManager.writeMirrorMeta.bind(this.metaManager),
            this.metaManager.toMirrorMetaFromServer.bind(this.metaManager),
            this.metaManager.writeLocalMeta.bind(this.metaManager),
            this.metaManager.writeLocalGroupMeta.bind(this.metaManager),
            this.metaManager.writeLocalScriptAndUpdateMeta.bind(this.metaManager),
        );

        // 设置依赖注入（合并管理器）
        this.mergeManager.setDependencies(
            (mirrorRoot: vscode.Uri, type: MagicResourceType, groupPathSub: string | undefined, fileName: string) => this.metaManager.readLocalMeta(mirrorRoot, type, groupPathSub, fileName),
            (info: any) => this.metaManager.toMirrorMetaFromServer(info),
            (meta: MirrorFileMeta | null) => this.metaManager.sanitizeMetaForDiff(meta)
        );

        // 设置联动管理器依赖
        this.linkageManager.setDependencies(this.metaManager);
    }

    public static getInstance(context?: vscode.ExtensionContext): MirrorWorkspaceManager {
        if (!MirrorWorkspaceManager.instance) {
            if (!context) {
                throw new Error('MirrorWorkspaceManager requires an ExtensionContext for initialization');
            }
            MirrorWorkspaceManager.instance = new MirrorWorkspaceManager(context);
        }
        return MirrorWorkspaceManager.instance;
    }

    // 读取/写入镜像连接偏好（按文件夹路径存储，范围：全局用户）
    public async getMirrorConnectPreference(root: vscode.Uri): Promise<'yes' | 'no' | undefined> {
        return this.preferenceManager.getMirrorConnectPreference(root);
    }

    public async setMirrorConnectPreference(root: vscode.Uri, pref: 'yes' | 'no'): Promise<void> {
        return this.preferenceManager.setMirrorConnectPreference(root, pref);
    }

    // 获取默认镜像根目录（基于服务器 URL 生成稳定目录名）
    public getDefaultMirrorRoot(serverConfig: MagicServerConfig): vscode.Uri {
        return this.preferenceManager.getDefaultMirrorRoot(serverConfig);
    }

    // 判断当前工作区是否为镜像工作区
    public async isMirrorWorkspace(folder: vscode.Uri): Promise<boolean> {
        return this.workspaceUtils.isMirrorWorkspace(folder);
    }

    public async readMirrorMeta(folder: vscode.Uri): Promise<MirrorMeta | null> {
        return this.metaManager.readMirrorMeta(folder);
    }


    // 刷新写入工作台补全数据（classes/extensions/functions）到镜像元数据
    public async refreshWorkbenchCompletionData(client: MagicApiClient, root: vscode.Uri, progress?: vscode.Progress<{ message?: string; increment?: number }>): Promise<void> {
        await this.syncManager.refreshWorkbenchCompletionData(client, root, progress);
    }

    private toPosix(rel: string): string {
        return rel.replace(/\\/g, '/');
    }


    // 将服务端的虚拟FS全量同步到指定本地镜像根目录
    public async syncFromServer(
        client: MagicApiClient,
        root: vscode.Uri,
        serverConfig?: MagicServerConfig,
        progress?: vscode.Progress<{ message?: string; increment?: number }>
    ): Promise<void> {
        return this.syncManager.syncFromServer(client, root, serverConfig, progress);
    }

    // 打开镜像工作区（会进行一次全量同步）
    public async openMirrorWorkspace(client: MagicApiClient, targetFolder?: vscode.Uri, serverConfig?: MagicServerConfig): Promise<void> {
        if (!serverConfig && !targetFolder) {
            throw new Error('openMirrorWorkspace 需要提供 serverConfig 或目标文件夹');
        }
        // 若提供了目标文件夹，则优先使用该目录；否则使用默认镜像根目录（根据 URL）
        const root = targetFolder ?? (serverConfig ? this.getDefaultMirrorRoot(serverConfig) : undefined);
        if (!root) {
            throw new Error('无法确定镜像根目录');
        }
        await vscode.window.withProgress({
            location: vscode.ProgressLocation.Notification,
            title: '创建镜像工作区',
            cancellable: false
        }, async (progress) => {
            // 写入镜像根信息标记文件
            progress.report({ message: '写入镜像元数据' });
            const baseMeta: MirrorMeta = {
                createdAt: Date.now(),
                url: serverConfig?.url,
                token: serverConfig?.token,
                username: serverConfig?.username,
                password: serverConfig?.password,
                lspPort: (serverConfig as any)?.lspPort,
                debugPort: (serverConfig as any)?.debugPort,
            };
            await this.metaManager.writeMirrorMeta(root, baseMeta);
            // 刷新写入本地补全用的工作台数据
            progress.report({ message: '拉取工作台补全数据' });
            await this.refreshWorkbenchCompletionData(client, root, progress);
            // 调用已有的全量同步逻辑（同步到选定根目录）
            progress.report({ message: '同步服务器资源与脚本' });
            await this.syncFromServer(client, root, serverConfig, progress);
            // 在当前窗口切换到镜像工作区（替换工作区文件夹）
            progress.report({ message: '打开镜像工作区（当前窗口）' });
            const curFolders = vscode.workspace.workspaceFolders;
            const nameFromCfg = serverConfig?.url ? `Mirror: ${new URL(serverConfig.url).host}` : 'Magic API Mirror';
            const replaced = curFolders && curFolders.length > 0
                ? vscode.workspace.updateWorkspaceFolders(0, curFolders.length, { uri: root, name: nameFromCfg })
                : false;
            if (!replaced) {
                // 无活动工作区时，回退到 openFolder（保持不新开窗口）
                await vscode.commands.executeCommand('vscode.openFolder', root, { forceNewWindow: false });
            }

            // 启动镜像监听，确保当前窗口即可进行推送/同步
            try {
                const disposables = this.startMirrorListeners(client, root);
                this.context.subscriptions.push(...disposables);
            } catch(e) {console.warn(e)}
        });
    }

    // 启动镜像工作区监听：保存/创建/删除/重命名 -> 推送到服务端
    public startMirrorListeners(client: MagicApiClient, mirrorRoot: vscode.Uri): vscode.Disposable[] {
        const disposables: vscode.Disposable[] = [];

        const isUnderMirror = (uri: vscode.Uri) => {
            if (uri.scheme !== MIRROR_FILE_CONSTANTS.FILE_SCHEME) return false;
            const mirrorFs = mirrorRoot.fsPath;
            const fsPath = uri.fsPath;
            // 处理大小写与分隔符
            const norm = (p: string) => path.resolve(p);
            return norm(fsPath).toLowerCase().startsWith(norm(mirrorFs).toLowerCase());
        };

        const parseRel = (uri: vscode.Uri) => {
            const rel = this.toPosix(path.relative(mirrorRoot.fsPath, uri.fsPath));
            const parts = rel.split('/').filter(Boolean);
            const last = parts[parts.length - 1] || '';
            // 更稳健地定位资源类型段：在路径中寻找第一个顶层类型标识
            const typeIndex = parts.findIndex(seg => (MAGIC_RESOURCE_TYPES as readonly string[]).includes(seg));
            const type = (typeIndex >= 0 ? parts[typeIndex] : parts[0]) as MagicResourceType;
            const ext = type === 'datasource' ? '.json' : '.ms';
            const isMsFile = last.endsWith(ext);
            const isGroupMetaFile = last === MIRROR_FILE_CONSTANTS.GROUP_META_FILE;
            const isMetaFile = last.startsWith('.') && last.endsWith(MIRROR_FILE_CONSTANTS.META_FILE_EXTENSION) && !isGroupMetaFile;
            const isFile = isMsFile || isMetaFile || isGroupMetaFile;
            const groupStart = typeIndex >= 0 ? typeIndex + 1 : 1;
            const groupPathSub = isFile ? parts.slice(groupStart, parts.length - 1).join('/') : parts.slice(groupStart).join('/');
            const fileName = isMsFile ? parts[parts.length - 1].slice(0, -ext.length)
                : isMetaFile ? parts[parts.length - 1].replace(/^\./, '').replace(new RegExp(`\\${MIRROR_FILE_CONSTANTS.META_FILE_EXTENSION}$`), '')
                : undefined;
            const dir = `${type}${groupPathSub ? '/' + groupPathSub : ''}`;
            return { type, groupPathSub, fileName, dir, isFile, isMsFile, isMetaFile, isGroupMetaFile };
        };

        // 分组目录存在性保障（从 groupPathSub 逐级创建缺失分组）
        const ensureGroupsLocal = async (type: MagicResourceType, groupPathSub: string): Promise<void> => {
            await this.syncManager.ensureGroups(client, type, groupPathSub);
        };

        // 一次性保存拦截集合（按镜像根）
        const supSet = this.suppressNextSavePushKeys.get(mirrorRoot.fsPath) ?? new Set<string>();
        this.suppressNextSavePushKeys.set(mirrorRoot.fsPath, supSet);
        const markSuppressNextSave = (type: MagicResourceType, dir: string, fileName: string) => {
            const msKey = `${dir}/${fileName}${type === 'datasource' ? '.json' : '.ms'}`;
            const metaKey = `${dir}/.${fileName}${MIRROR_FILE_CONSTANTS.META_FILE_EXTENSION}`;
            supSet.add(msKey);
            supSet.add(metaKey);
        };

        // 文档保存 -> 推送脚本/元数据到服务端，并更新本地元数据时间戳（优先使用 .meta.json 的 id 关联）
        // 用户需求：添加保存同步进度展示，提升用户在网络慢时的体验
        disposables.push(vscode.workspace.onDidSaveTextDocument(async (doc) => {
            try {
                if (doc.uri.scheme !== MIRROR_FILE_CONSTANTS.FILE_SCHEME || !isUnderMirror(doc.uri)) return;
                const relOpen = this.toPosix(path.relative(mirrorRoot.fsPath, doc.uri.fsPath));
                if (relOpen.startsWith(`${MIRROR_FILE_CONSTANTS.MERGE_DIR}/`)) return;
                const { type, groupPathSub, fileName, dir, isFile, isMsFile, isMetaFile, isGroupMetaFile } = parseRel(doc.uri);
                if (!isFile || !fileName || !MAGIC_RESOURCE_TYPES.includes(type)) return;

                // 一次性拦截：若由程序性写入触发的保存，跳过推送
                {
                    const supSet = this.suppressNextSavePushKeys.get(mirrorRoot.fsPath);
                    if (supSet) {
                        const msKey = `${dir}/${fileName}${type === 'datasource' ? '.json' : '.ms'}`;
                        const metaKey = `${dir}/.${fileName}${MIRROR_FILE_CONSTANTS.META_FILE_EXTENSION}`;
                        const keyToCheck = isMsFile ? msKey : (isMetaFile ? metaKey : '');
                        if (keyToCheck && supSet.has(keyToCheck)) {
                            supSet.delete(keyToCheck);
                            return; // 跳过一次服务器推送
                        }
                    }
                }

                // 如果保存的是 .group-meta.json：仅重建 fullUrlPath（无需推送服务器）
                if (isGroupMetaFile) {
                    try {
                        const gm = JSON.parse(doc.getText());
                        await this.linkageManager.handleGroupMetaSaved(mirrorRoot, type, groupPathSub, gm);
                    } catch (e) {
                        vscode.window.showErrorMessage(`解析分组元数据失败: ${String(e)}`);
                    }
                    return;
                }

                // 读取现有本地元数据或构建默认
                let meta = await this.metaManager.readLocalMeta(mirrorRoot, type, groupPathSub, fileName);
                // 确保 meta 非 null
                if (!meta) {
                    meta = {
                        name: fileName,
                        type,
                        groupPath: dir,
                        // localUpdateTime 迁移至 extra.autoGeneration 由 touchLocalMetaTime 负责
                    };
                }

                // 如果保存的是 .ms 文件，拿脚本内容；如果保存的是 .meta.json，则解析 meta
                let script: string = '';
                if (isMsFile) {
                    script = doc.getText();
                    meta = this.metaManager.touchLocalMetaTime(meta);
                    await this.linkageManager.touchAndWriteMeta(mirrorRoot, meta);
                } else if (isMetaFile) {
                    try {
                        const text = doc.getText();
                        const parsed = JSON.parse(text);
                        // 忽略扩展字段：仅用于展示，不参与同步
                        try { delete parsed.extra; } catch(e) {console.warn(e)}
                        // 校验元数据
                        const check = this.metaManager.validateLocalMeta({ ...meta, ...parsed }, fileName);
                        if (!check.ok) {
                            vscode.window.showErrorMessage(`元数据校验失败: ${check.errors.join('; ')}`);
                            return;
                        }
                        // 应用联动：根据 name/path/requestMapping 可能重命名相邻脚本与 meta
                        const applied = await this.linkageManager.applyMetaSaveAndSyncNames(mirrorRoot, type, groupPathSub, fileName, parsed);
                        meta = applied.meta;
                        // 如果发生了重命名，交由重命名事件处理服务器侧与本地后续逻辑
                        if (applied.fileName !== fileName) {
                            return;
                        }
                    } catch (e) {
                        vscode.window.showErrorMessage(`解析元数据失败: ${String(e)}`);
                        return;
                    }
                    // 读取相邻脚本文件（按类型选择扩展名）
                    const msUri = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${type === 'datasource' ? '.json' : '.ms'}`);
                    try {
                        const buf = await vscode.workspace.fs.readFile(msUri);
                        script = Buffer.from(buf).toString('utf8');
                    } catch {
                        script = '';
                    }
                }

                // 进一步修复：加强 null 检查和类型断言，确保进入同步时 meta 非 null
                if (!meta) {
                    vscode.window.showErrorMessage('Meta 加载失败');
                    return;
                }

                // 同步到服务器：优先使用本地 meta.id 更新；缺失或无效时再按路径查找并创建
                await vscode.window.withProgress({
                    location: vscode.ProgressLocation.Notification,
                    title: `保存 ${fileName} 到服务器`,
                    cancellable: false
                }, async (progress) => {
                    // 再次确认 meta 非 null（类型缩小）
                    if (!meta) return;

                    progress.report({ message: '正在检查服务器文件...', increment: 20 });

                    let targetInfo: any | null = null;
                    let targetId: string | undefined = meta.id;

                    // 1) 有 id 时优先按 id 获取文件信息
                    if (targetId) {
                        try {
                            const byId = await client.getFile(targetId);
                            if (byId) {
                                targetInfo = byId;
                            }
                        } catch(e) {console.warn(e)}
                    }

                    // 2) 无 id 或 id 无效，则按目录刷新并用路径查找 id
                    if (!targetInfo) {
                        await client.getResourceFiles(dir);
                        const fid = client.getFileIdByPath(`${dir}/${fileName}${type === 'datasource' ? '.json' : '.ms'}`);
                        if (fid) {
                            const byPath = await client.getFile(fid);
                            if (byPath) {
                                targetInfo = byPath;
                                targetId = fid;
                                // 若本地 meta 没有 id，则补充写入，后续保存直接走 id
                                if (!meta.id) {
                                    meta.id = fid;
                                    await this.linkageManager.touchAndWriteMeta(mirrorRoot, meta);
                                }
                            }
                        }
                    }

                    progress.report({ message: '正在推送脚本和元数据...', increment: 50 });

                    if (targetInfo && targetId) {
                        const ok = await client.saveFile({
                            ...targetInfo,
                            id: targetId,
                            name: meta!.name || fileName,
                            path: meta!.path || targetInfo.path || '',
                            method: meta!.method || targetInfo.method,
                            requestMapping: meta!.requestMapping || targetInfo.requestMapping,
                            description: meta!.description || targetInfo.description,
                            script,
                            // 类型专属字段：API
                            ...(meta!.type === 'api' ? {
                                params: meta!.params,
                                headers: meta!.headers,
                                contentType: meta!.contentType,
                                timeout: meta!.timeout,
                            } : {}),
                            // 类型专属字段：任务
                            ...(meta!.type === 'task' ? {
                                cron: meta!.cron,
                                enabled: meta!.enabled,
                                executeOnStart: meta!.executeOnStart,
                            } : {}),
                        } as any);
                        if (ok) {
                            const fresh = await client.getFile(targetId);
                            if (fresh && meta) {
                                meta.updateTime = fresh.updateTime;
                                // 补齐 groupId 等字段，增强后续保存的稳定性
                                if (fresh.groupId) meta.groupId = fresh.groupId;
                                progress.report({ message: '正在更新本地时间戳...', increment: 20 });
                                // 标记一次性拦截，避免本地写入触发回推
                                markSuppressNextSave(type, dir, fileName);
                                await this.linkageManager.touchAndWriteMeta(mirrorRoot, meta);
                            }
                        }
                    } else {
                        // 3) 既没有 id 也无法通过路径定位，走创建流程
                        await ensureGroupsLocal(type, groupPathSub);
                        const newId = await client.createFile({
                            name: fileName,
                            script,
                            type,
                            groupPath: groupPathSub ? `${type}/${groupPathSub}` : `${type}`,
                            method: meta!.method,
                            requestMapping: meta!.requestMapping,
                            description: meta!.description,
                            ...(type === 'api' ? {
                                params: meta!.params,
                                headers: meta!.headers,
                                contentType: meta!.contentType,
                                timeout: meta!.timeout,
                            } : {}),
                            ...(type === 'task' ? {
                                cron: meta!.cron,
                                enabled: meta!.enabled,
                                executeOnStart: meta!.executeOnStart,
                            } : {}),
                        } as any);
                        const msUri = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${type === 'datasource' ? '.json' : '.ms'}`);
                        if (!newId) {
                            const detail = client.getLastErrorMessage();
                            addFileError(msUri, detail ? `同步失败：${detail}` : '同步失败');
                        } else {
                            clearFileErrors(msUri);
                        }
                        if (newId && meta) {
                            meta.id = newId;
                            const fresh = await client.getFile(newId);
                            if (fresh?.groupId) meta.groupId = fresh.groupId;
                            meta.updateTime = fresh?.updateTime;
                            progress.report({ message: '正在更新本地时间戳...', increment: 20 });
                            // 标记一次性拦截，避免本地写入触发回推
                            markSuppressNextSave(type, dir, fileName);
                            await this.linkageManager.touchAndWriteMeta(mirrorRoot, meta);
                        }
                    }

                    progress.report({ message: '同步完成', increment: 10 });
                });
            } catch (e) {
                vscode.window.showErrorMessage(`推送保存失败: ${String(e)}`);
            }
        }));

        // 针对 magic-api 虚拟文档的保存：当用户在合并视图中编辑服务器侧并保存时，将内容同步到本地镜像
        disposables.push(vscode.workspace.onDidSaveTextDocument(async (doc) => {
            try {
                if (doc.uri.scheme !== MIRROR_FILE_CONSTANTS.MAGIC_API_SCHEME) return;
                // 解析 magic-api:/<dir>/<fileName>.ms
                const relPath = this.toPosix(doc.uri.path.replace(/^\//, ''));
                const parts = relPath.split('/');
                const file = parts.pop() || '';
                if (!file.endsWith(MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION)) return;
                const fileName = file.replace(new RegExp(`\\${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}$`), '');
                const dir = parts.join('/');
                const segs = dir.split('/');
                const type = segs[0] as MagicResourceType;
                const groupPathSub = segs.slice(1).join('/');
                if (!MAGIC_RESOURCE_TYPES.includes(type)) return;

                // 获取服务器最新元信息并更新本地；统一拦截器写入脚本并更新 meta
                await client.getResourceFiles(dir);
                const fid = client.getFileIdByPath(`${dir}/${fileName}.ms`);
                const serverInfo = fid ? await client.getFile(fid) : null;
                const serverMetaPatch = serverInfo ? this.metaManager.toMirrorMetaFromServer(serverInfo) : undefined;
                await this.metaManager.writeLocalScriptAndUpdateMeta(
                    mirrorRoot,
                    type,
                    groupPathSub,
                    fileName,
                    doc.getText(),
                    serverMetaPatch
                );
            } catch (e) {
                vscode.window.showErrorMessage(`同步服务器保存到本地镜像失败: ${String(e)}`);
            }
        }));

        // 合并临时文件保存：将合并结果同步到本地真实脚本与服务器，并清理临时文件
        disposables.push(vscode.workspace.onDidSaveTextDocument(async (doc) => {
            try {
                if (doc.uri.scheme !== 'file' || !isUnderMirror(doc.uri)) return;
                const rel = this.toPosix(path.relative(mirrorRoot.fsPath, doc.uri.fsPath));
                if (!rel.startsWith('.merge/')) return;

                const parts = rel.replace(/^\.merge\//, '').split('/');
                const file = parts.pop() || '';
                if (!file.endsWith('.ms')) return;
                const fileName = file.replace(/\.ms$/, '');
                const dir = parts.join('/');
                const segs = dir.split('/');
                const type = segs[0] as MagicResourceType;
                const groupPathSub = segs.slice(1).join('/');
                if (!MAGIC_RESOURCE_TYPES.includes(type)) return;

                const finalScript = doc.getText();
                await client.getResourceFiles(dir);
                const fid = client.getFileIdByPath(`${dir}/${fileName}.ms`);
                const serverInfo = fid ? await client.getFile(fid) : null;
                const existingMeta = await this.metaManager.readLocalMeta(mirrorRoot, type, groupPathSub, fileName);
                const metaToUse: MirrorFileMeta = this.metaManager.touchLocalMetaTime({
                    ...(existingMeta || { name: fileName, type, groupPath: dir }),
                    updateTime: serverInfo?.updateTime,
                    groupId: (serverInfo as any)?.groupId || existingMeta?.groupId,
                    groupPath: dir,
                });
                await this.metaManager.writeLocalScriptAndUpdateMeta(
                    mirrorRoot,
                    type,
                    groupPathSub,
                    fileName,
                    finalScript,
                    metaToUse
                );

                if (serverInfo) {
                    const ok = await client.saveFile({
                        ...serverInfo,
                        name: metaToUse.name,
                        path: metaToUse.path || serverInfo.path || '',
                        method: metaToUse.method || serverInfo.method,
                        requestMapping: metaToUse.requestMapping || serverInfo.requestMapping,
                        description: metaToUse.description || serverInfo.description,
                        script: finalScript,
                        ...(type === 'api' ? {
                            params: metaToUse.params ?? (serverInfo as any).params,
                            headers: metaToUse.headers ?? (serverInfo as any).headers,
                            contentType: metaToUse.contentType ?? (serverInfo as any).contentType,
                            timeout: metaToUse.timeout ?? (serverInfo as any).timeout,
                        } : {}),
                        ...(type === 'task' ? {
                            cron: metaToUse.cron ?? (serverInfo as any).cron,
                            enabled: metaToUse.enabled ?? (serverInfo as any).enabled,
                            executeOnStart: metaToUse.executeOnStart ?? (serverInfo as any).executeOnStart,
                        } : {}),
                    } as any);
                    if (!ok) {
                        const detail = client.getLastErrorMessage();
                        addFileError(doc.uri, detail ? `合并保存失败：${detail}` : '合并保存失败：服务器未接受更改');
                    } else {
                        clearFileErrors(doc.uri);
                        // 推送成功后，主动拉取服务器meta并写回本地（带拦截）
                        try {
                            if (fid) {
                                const fresh = await client.getFile(fid);
                                if (fresh) {
                                    const patchedMeta: MirrorFileMeta = this.metaManager.touchLocalMetaTime({
                                        ...metaToUse,
                                        updateTime: fresh.updateTime,
                                        groupId: (fresh as any).groupId ?? metaToUse.groupId,
                                        groupPath: dir,
                                    });
                                    markSuppressNextSave(type, dir, fileName);
                                    await this.linkageManager.touchAndWriteMeta(mirrorRoot, patchedMeta);
                                }
                            }
                        } catch(e) {console.warn(e)}
                    }
                } else {
                    await this.syncManager.ensureGroups(client, type, groupPathSub);
                    const id = await client.createFile({
                        name: fileName,
                        script: finalScript,
                        type,
                        groupPath: dir,
                        method: metaToUse.method,
                        requestMapping: metaToUse.requestMapping,
                        description: metaToUse.description,
                        ...(type === 'api' ? {
                            params: metaToUse.params,
                            headers: metaToUse.headers,
                            contentType: metaToUse.contentType,
                            timeout: metaToUse.timeout,
                        } : {}),
                        ...(type === 'task' ? {
                            cron: metaToUse.cron,
                            enabled: metaToUse.enabled,
                            executeOnStart: metaToUse.executeOnStart,
                        } : {}),
                    } as any);
                    if (!id) {
                        const detail = client.getLastErrorMessage();
                        addFileError(doc.uri, detail ? `合并创建失败：${detail}` : '合并创建失败：服务器未返回新文件ID');
                    } else {
                        clearFileErrors(doc.uri);
                        const fresh = await client.getFile(id);
                        if (fresh) {
                            const patchedMeta: MirrorFileMeta = this.metaManager.touchLocalMetaTime({
                                ...(metaToUse || { name: fileName, type, groupPath: dir }),
                                id,
                                updateTime: fresh.updateTime,
                                groupId: (fresh as any).groupId ?? (metaToUse as any)?.groupId,
                                groupPath: dir,
                            });
                            markSuppressNextSave(type, dir, fileName);
                            await this.linkageManager.touchAndWriteMeta(mirrorRoot, patchedMeta);
                        }
                    }
                }

                // 可选：清理合并临时文件
                try { await vscode.workspace.fs.delete(doc.uri, { useTrash: false }); } catch(e) {console.warn(e)}
                await this.mergeManager.cleanupMergeSubtree(mirrorRoot, dir);
                vscode.window.showInformationMessage(`${fileName}.ms 合并结果已同步到本地与服务器`);
            } catch (e) {
                vscode.window.showErrorMessage(`保存合并结果失败: ${String(e)}`);
            }
        }));

        // 元数据合并临时文件保存：将合并后的 .meta.json 应用到本地并同步服务器
        disposables.push(vscode.workspace.onDidSaveTextDocument(async (doc) => {
            try {
                if (doc.uri.scheme !== 'file' || !isUnderMirror(doc.uri)) return;
                const rel = this.toPosix(path.relative(mirrorRoot.fsPath, doc.uri.fsPath));
                if (!rel.startsWith('.merge/')) return;

                const parts = rel.replace(/^\.merge\//, '').split('/');
                const file = parts.pop() || '';
                if (!file.endsWith('.meta.json')) return;
                const fileName = file.replace(/^\./, '').replace(/\.meta\.json$/, '');
                const dir = parts.join('/');
                const segs = dir.split('/');
                const type = segs[0] as MagicResourceType;
                const groupPathSub = segs.slice(1).join('/');
                if (!MAGIC_RESOURCE_TYPES.includes(type)) return;

                // 解析最终合并后的元数据
                let finalMetaObj: any;
                try { finalMetaObj = JSON.parse(doc.getText()); } catch (e) {
                    vscode.window.showErrorMessage(`无法解析合并后的元数据 JSON: ${String(e)}`);
                    return;
                }

                // 忽略本地字段：不参与最终合并
                try { delete finalMetaObj.localUpdateTime; } catch(e) {console.warn(e)}
                // 忽略扩展字段：不参与最终合并与同步（服务端不接收）
                try { delete finalMetaObj.extra; } catch(e) {console.warn(e)}

                // 写入到本地 .meta.json 并更新时间戳
                const existingMeta = await this.metaManager.readLocalMeta(mirrorRoot, type, groupPathSub, fileName);
                const mergedMeta: MirrorFileMeta = this.metaManager.touchLocalMetaTime({
                    ...(existingMeta || { name: fileName, type, groupPath: dir }),
                    ...(finalMetaObj || {}),
                    groupPath: dir,
                });
                markSuppressNextSave(type, dir, fileName);
                await this.linkageManager.touchAndWriteMeta(mirrorRoot, mergedMeta);

                // 同步到服务器：保留现有脚本
                await client.getResourceFiles(dir);
                let fid = mergedMeta.id || client.getFileIdByPath(`${dir}/${fileName}.ms`);
                let serverInfo = fid ? await client.getFile(fid) : null;
                const msUri = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                let localScript = '';
                try { localScript = Buffer.from(await vscode.workspace.fs.readFile(msUri)).toString('utf8'); } catch(e) {console.warn(e)}

                if (!serverInfo) {
                    await this.syncManager.ensureGroups(client, type, groupPathSub);
                    const newId = await client.createFile({
                        name: fileName,
                        script: localScript,
                        type,
                        groupPath: dir,
                        method: mergedMeta.method,
                        requestMapping: mergedMeta.requestMapping,
                        description: mergedMeta.description,
                        ...(type === 'api' ? {
                            params: mergedMeta.params,
                            headers: mergedMeta.headers,
                            contentType: mergedMeta.contentType,
                            timeout: mergedMeta.timeout,
                        } : {}),
                        ...(type === 'task' ? {
                            cron: mergedMeta.cron,
                            enabled: mergedMeta.enabled,
                            executeOnStart: mergedMeta.executeOnStart,
                        } : {}),
                    } as any);
                    if (newId) {
                        fid = newId;
                        serverInfo = await client.getFile(newId);
                        mergedMeta.id = newId;
                        clearFileErrors(doc.uri);
                    } else {
                        addFileError(doc.uri, '合并创建失败：服务器未返回新文件ID');
                    }
                } else {
                    const ok = await client.saveFile({
                        ...serverInfo,
                        name: mergedMeta.name || fileName,
                        path: mergedMeta.path || serverInfo.path || '',
                        method: mergedMeta.method || serverInfo.method,
                        requestMapping: mergedMeta.requestMapping || serverInfo.requestMapping,
                        description: mergedMeta.description || serverInfo.description,
                        script: localScript,
                        ...(type === 'api' ? {
                            params: mergedMeta.params ?? (serverInfo as any).params,
                            headers: mergedMeta.headers ?? (serverInfo as any).headers,
                            contentType: mergedMeta.contentType ?? (serverInfo as any).contentType,
                            timeout: mergedMeta.timeout ?? (serverInfo as any).timeout,
                        } : {}),
                        ...(type === 'task' ? {
                            cron: mergedMeta.cron ?? (serverInfo as any).cron,
                            enabled: mergedMeta.enabled ?? (serverInfo as any).enabled,
                            executeOnStart: mergedMeta.executeOnStart ?? (serverInfo as any).executeOnStart,
                        } : {}),
                    } as any);
                    if (!ok) {
                        const detail = client.getLastErrorMessage();
                        addFileError(doc.uri, detail ? `合并保存失败：${detail}` : '合并保存失败：服务器未接受更改');
                    } else {
                        clearFileErrors(doc.uri);
                    }
                }

                // 更新本地 meta 的服务器更新时间与分组ID
                if (fid) {
                    const fresh = await client.getFile(fid);
                    if (fresh) {
                        mergedMeta.updateTime = fresh.updateTime;
                        if ((fresh as any).groupId) mergedMeta.groupId = (fresh as any).groupId;
                        markSuppressNextSave(type, dir, fileName);
                        await this.linkageManager.touchAndWriteMeta(mirrorRoot, mergedMeta);
                    }
                }

                try { await vscode.workspace.fs.delete(doc.uri, { useTrash: false }); } catch(e) {console.warn(e)}
                await this.mergeManager.cleanupMergeSubtree(mirrorRoot, dir);
                vscode.window.showInformationMessage(`${fileName}.meta.json 合并结果已同步到本地与服务器`);
            } catch (e) {
                vscode.window.showErrorMessage(`保存元数据合并结果失败: ${String(e)}`);
            }
        }));

        // 文件创建事件（用于空文件的立即建档）
        disposables.push(vscode.workspace.onDidCreateFiles(async (e) => {
            for (const uri of e.files) {
                try {
                    if (uri.scheme !== 'file' || !isUnderMirror(uri)) continue;
                    const rel = this.toPosix(path.relative(mirrorRoot.fsPath, uri.fsPath));
                    if (rel.startsWith('.merge/')) continue;
                    const { type, groupPathSub, fileName, dir, isFile, isMsFile, isMetaFile } = parseRel(uri);
                    if (!isFile || !fileName || !MAGIC_RESOURCE_TYPES.includes(type)) continue;
                    await this.syncManager.ensureGroups(client, type, groupPathSub);
                    await client.getResourceFiles(dir);
                    const fid = client.getFileIdByPath(`${dir}/${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                    if (isMsFile) {
                        // 针对新建的 .ms 文件，若服务器不存在则创建文件，并创建对应的本地 meta
                        if (!fid) {
                            const newId = await client.createFile({
                                name: fileName,
                                script: '',
                                type,
                                groupPath: groupPathSub ? `${type}/${groupPathSub}` : `${type}`
                            });
                            if (!newId) {
                                const detail = client.getLastErrorMessage();
                                addFileError(uri, detail ? `同步失败：${detail}` : '同步失败');
                            } else {
                                clearFileErrors(uri);
                            }
                            const meta = await this.linkageManager.ensureInitMetaForScript(mirrorRoot, type, groupPathSub, fileName, { id: newId || undefined });
                            markSuppressNextSave(type, dir, fileName);
                            await this.linkageManager.touchAndWriteMeta(mirrorRoot, meta);
                        }
                    } else if (isMetaFile) {
                        // 新建 meta 文件：不主动推送，等待保存时解析并推送
                        // 但仍写入一个默认的初始值，确保格式正确
                        const initMeta: MirrorFileMeta = this.metaManager.touchLocalMetaTime({
                            name: fileName,
                            type,
                            groupPath: dir,
                        } as MirrorFileMeta);
                        markSuppressNextSave(type, dir, fileName);
                        await this.linkageManager.touchAndWriteMeta(mirrorRoot, initMeta);
                    }
                } catch (e2) {
                    vscode.window.showErrorMessage(`创建文件推送失败: ${String(e2)}`);
                }
            }
        }));

        // 文件删除事件
        disposables.push(vscode.workspace.onDidDeleteFiles(async (e) => {
            for (const uri of e.files) {
                try {
                    if (uri.scheme !== 'file' || !isUnderMirror(uri)) continue;
                    const rel = this.toPosix(path.relative(mirrorRoot.fsPath, uri.fsPath));
                    if (rel.startsWith('.merge/')) continue;
                    const { type, groupPathSub, fileName, dir, isFile, isMsFile, isMetaFile } = parseRel(uri);
                    if (!MAGIC_RESOURCE_TYPES.includes(type)) continue;
                    if (isMsFile && fileName) {
                        await client.getResourceFiles(dir);
                        const fid = client.getFileIdByPath(`${dir}/${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                        if (fid) {
                            await client.deleteFile(fid);
                        }
                        // 同步删除本地 meta 文件
                        await this.linkageManager.deleteMetaForScript(mirrorRoot, type, groupPathSub, fileName);
                    } else if (isMetaFile && fileName) {
                        // 删除元数据文件不直接删除服务器资源，仅忽略
                    }
                } catch (e3) {
                    vscode.window.showErrorMessage(`删除文件推送失败: ${String(e3)}`);
                }
            }
        }));

        // 重命名事件（文件或目录）
        disposables.push(vscode.workspace.onDidRenameFiles(async (e) => {
            for (const { oldUri, newUri } of e.files) {
                try {
                    if (!isUnderMirror(oldUri) || !isUnderMirror(newUri)) continue;
                    const oldRelPath = this.toPosix(path.relative(mirrorRoot.fsPath, oldUri.fsPath));
                    const newRelPath = this.toPosix(path.relative(mirrorRoot.fsPath, newUri.fsPath));
                    if (oldRelPath.startsWith('.merge/') || newRelPath.startsWith('.merge/')) continue;
                    const oldRel = parseRel(oldUri);
                    const newRel = parseRel(newUri);
                    if (oldRel.isMsFile && newRel.isMsFile && oldRel.fileName && newRel.fileName && MAGIC_RESOURCE_TYPES.includes(oldRel.type)) {
                        const dir = oldRel.dir;
                        await client.getResourceFiles(dir);
                        const fid = client.getFileIdByPath(`${dir}/${oldRel.fileName}.ms`);
                        if (fid) {
                            const info = await client.getFile(fid);
                            if (info) {
                                const ok = await client.saveFile({ ...info, name: newRel.fileName! });
                                if (!ok) {
                                    const detail = client.getLastErrorMessage();
                                    addFileError(newUri, detail ? `重命名保存失败：${detail}` : '重命名保存失败：服务器未接受更改');
                                } else {
                                    clearFileErrors(newUri);
                                }
                            }
                        }
                        // 同步重命名/移动本地 meta 文件
                        await this.linkageManager.renameMetaForScript(
                            mirrorRoot,
                            oldRel.type,
                            oldRel.groupPathSub,
                            oldRel.fileName,
                            newRel.type,
                            newRel.groupPathSub,
                            newRel.fileName!
                        );
                        // 拉取服务器最新信息并写回本地 meta（一次性拦截保存回推）
                        try {
                            const fresh = fid ? await client.getFile(fid) : null;
                            const existing = await this.metaManager.readLocalMeta(mirrorRoot, newRel.type, newRel.groupPathSub, newRel.fileName);
                            const patched: MirrorFileMeta = this.metaManager.touchLocalMetaTime({
                                ...(existing || (fresh ? this.metaManager.toMirrorMetaFromServer(fresh) : { name: newRel.fileName!, type: newRel.type, groupPath: newRel.dir })),
                                id: fresh?.id ? String(fresh.id) : (fid ? String(fid) : (existing?.id || undefined)),
                                name: newRel.fileName!,
                                groupPath: newRel.dir,
                                updateTime: fresh?.updateTime ?? existing?.updateTime,
                                groupId: (fresh as any)?.groupId ?? existing?.groupId,
                            });
                            markSuppressNextSave(newRel.type, newRel.dir, newRel.fileName!);
                            await this.linkageManager.touchAndWriteMeta(mirrorRoot, patched);
                        } catch(e) {console.warn(e)}

                    } else if (!oldRel.isFile && !newRel.isFile && MAGIC_RESOURCE_TYPES.includes(oldRel.type)) {
                        // 目录重命名 -> 分组重命名
                        const type = oldRel.type;
                        const oldPathSub = oldRel.groupPathSub;
                        const newSeg = newRel.groupPathSub.split('/').pop() || '';
                        await client.getGroups(type);
                        const gid = client.getGroupIdByPath(`${type}/${oldPathSub}`);
                        if (gid) {
                            const groupInfo = await client.getGroup(gid);
                            if (groupInfo) {
                                await client.saveGroup({ ...groupInfo, name: newSeg });
                            }
                        }
                        // 本地联动：刷新新目录下所有 .meta.json 的 groupPath/fullUrlPath
                        try { await this.linkageManager.refreshGroupPathUnder(mirrorRoot, type, newRel.groupPathSub); } catch(e) {console.warn(e)}
                    }
                } catch (e4) {
                    vscode.window.showErrorMessage(`重命名推送失败: ${String(e4)}`);
                }
            }
        }));

        // 文档打开事件：检查本地与服务器差异并提供合并操作
        disposables.push(vscode.workspace.onDidOpenTextDocument(async (doc) => {
            try {
                if (doc.uri.scheme !== 'file' || !isUnderMirror(doc.uri)) return;
                const { type, groupPathSub, fileName, dir, isFile, isMsFile, isMetaFile } = parseRel(doc.uri);
                if (!isFile || !fileName || !MAGIC_RESOURCE_TYPES.includes(type)) return;

                await client.getResourceFiles(dir);
                const fid = client.getFileIdByPath(`${dir}/${fileName}.ms`);
                const info = fid ? await client.getFile(fid) : null;
                const localMeta = await this.metaManager.readLocalMeta(mirrorRoot, type, groupPathSub, fileName);

                if (isMsFile) {
                    const localScript = doc.getText();
                    const serverScript = info?.script ?? '';
                    const normalizeScript = (s: string) => s.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
                    const scriptsEqual = normalizeScript(localScript) === normalizeScript(serverScript);
                    const serverTime = info?.updateTime ?? 0;
                    const localTime = this.metaManager.getLocalUpdateTime(localMeta);
                    if (!scriptsEqual) {
                        let choice = null 
                        if(serverTime != localMeta?.updateTime ) {
                            choice = await vscode.window.showInformationMessage(
                            `检测到 ${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION} 本地与服务器内容不一致，是否进行合并？`,
                            '打开合并', '以本地为准', '以服务器为准'
                        );
                        } else if(localTime > serverTime) {
                            choice = "以本地为准"
                        }
                        
                        if (choice === '打开合并') {
                            await this.mergeManager.promptMergeForFile(fileName, dir, mirrorRoot);
                        } else if (choice === '以本地为准' || choice === '以服务器为准') {
                            const useLocal = choice === '以本地为准' || localTime >= serverTime;
                            if (!useLocal) {
                                // 以服务器为准：仅写本地脚本与元数据，并设置一次性保存拦截
                                const serverMetaPatch = info ? this.metaManager.toMirrorMetaFromServer(info) : undefined;
                                await this.metaManager.writeLocalScriptAndUpdateMeta(
                                    mirrorRoot,
                                    type,
                                    groupPathSub,
                                    fileName,
                                    serverScript,
                                    serverMetaPatch
                                );
                                markSuppressNextSave(type, dir, fileName);
                                return;
                            }
                            const finalScript = localScript;
                            const metaToUse: MirrorFileMeta = {
                                ...(localMeta || { name: fileName, type, groupPath: dir }),
                                updateTime: info?.updateTime,
                                // 本地时间戳由 touchLocalMetaTime 写入 extra.autoGeneration
                            };
                            await this.linkageManager.touchAndWriteMeta(mirrorRoot, this.metaManager.touchLocalMetaTime(metaToUse));
                            if (fid && info) {
                                const ok = await client.saveFile({
                                    ...info,
                                    name: metaToUse.name,
                                    path: metaToUse.path || info.path || '',
                                    method: metaToUse.method || info.method,
                                    requestMapping: metaToUse.requestMapping || info.requestMapping,
                                    description: metaToUse.description || info.description,
                                    // 类型专属字段：API
                                    ...(metaToUse.type === 'api' ? {
                                        params: metaToUse.params ?? (info as any).params,
                                        headers: metaToUse.headers ?? (info as any).headers,
                                        contentType: metaToUse.contentType ?? (info as any).contentType,
                                        timeout: metaToUse.timeout ?? (info as any).timeout,
                                    } : {}),
                                    // 类型专属字段：任务
                                    ...(metaToUse.type === 'task' ? {
                                        cron: metaToUse.cron ?? (info as any).cron,
                                        enabled: metaToUse.enabled ?? (info as any).enabled,
                                        executeOnStart: metaToUse.executeOnStart ?? (info as any).executeOnStart,
                                    } : {}),
                                    script: finalScript,
                                } as any);
                                if (!ok) {
                                    const detail = client.getLastErrorMessage();
                                    addFileError(doc.uri, detail ? `保存失败：${detail}` : '保存失败：服务器未接受更改');
                                } else {
                                    clearFileErrors(doc.uri);
                                }
                            } else {
                                await ensureGroupsLocal(type, groupPathSub);
                                const id = await client.createFile({
                                    name: fileName,
                                    script: finalScript,
                                    type,
                                    groupPath: dir,
                                    method: metaToUse.method,
                                    requestMapping: metaToUse.requestMapping,
                                    description: metaToUse.description,
                                    // 类型专属字段：API
                                    ...(type === 'api' ? {
                                        params: metaToUse.params,
                                        headers: metaToUse.headers,
                                        contentType: metaToUse.contentType,
                                        timeout: metaToUse.timeout,
                                    } : {}),
                                    // 类型专属字段：任务
                                    ...(type === 'task' ? {
                                        cron: metaToUse.cron,
                                        enabled: metaToUse.enabled,
                                        executeOnStart: metaToUse.executeOnStart,
                                    } : {}),
                                });
                                if (!id) {
                                    const detail = client.getLastErrorMessage();
                                    addFileError(doc.uri, detail ? `创建失败：${detail}` : '创建失败：服务器未返回新文件ID');
                                } else {
                                    clearFileErrors(doc.uri);
                                }
                            }
                        }
                    }
                } else if (isMetaFile) {
                    // 比较元数据差异
                    let text = doc.getText();
                    let local: MirrorFileMeta | null = null;
                    try { local = JSON.parse(text); } catch { local = localMeta || null; }
                    // 元数据校验（本地）
                    if (local) {
                        const check = this.metaManager.validateLocalMeta(local, fileName);
                        if (!check.ok) {
                            vscode.window.showWarningMessage(`当前元数据存在不规范项：${check.errors.join('; ')}`);
                        }
                    }
                    const serverMeta = info ? this.metaManager.toMirrorMetaFromServer(info) : null;
                    if (serverMeta && local) {
                        const localLocalTime = this.metaManager.getLocalUpdateTime(local);
                        const localUpdateTime = local.updateTime ?? 0;
                        const serverUpdateTime = serverMeta.updateTime ?? 0;
                        const { equal } = this.metaManager.compareMeta(local, serverMeta);
                        const autoOverwriteLocal = (localUpdateTime === serverUpdateTime) && (localLocalTime > serverUpdateTime);

                        if (autoOverwriteLocal && !equal) {
                            const finalMeta = local;
                            if (finalMeta) {
                                const touched = this.metaManager.touchLocalMetaTime(finalMeta);
                                await this.linkageManager.touchAndWriteMeta(mirrorRoot, touched);
                                // 合并到服务器：读取脚本并保存
                                const msUri = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                                let script = '';
                                try { script = Buffer.from(await vscode.workspace.fs.readFile(msUri)).toString('utf8'); } catch(e) {console.warn(e)}
                                if (fid && info) {
                                    const ok = await client.saveFile({
                                        ...info,
                                        name: finalMeta.name,
                                        path: finalMeta.path || info.path || '',
                                        method: finalMeta.method || info.method,
                                        requestMapping: finalMeta.requestMapping || info.requestMapping,
                                        description: finalMeta.description || info.description,
                                        // 类型专属字段：API
                                        ...(finalMeta.type === 'api' ? {
                                            params: finalMeta.params ?? (info as any).params,
                                            headers: finalMeta.headers ?? (info as any).headers,
                                            contentType: finalMeta.contentType ?? (info as any).contentType,
                                            timeout: finalMeta.timeout ?? (info as any).timeout,
                                        } : {}),
                                        // 类型专属字段：任务
                                        ...(finalMeta.type === 'task' ? {
                                            cron: finalMeta.cron ?? (info as any).cron,
                                            enabled: finalMeta.enabled ?? (info as any).enabled,
                                            executeOnStart: finalMeta.executeOnStart ?? (info as any).executeOnStart,
                                        } : {}),
                                        script
                                    } as any);
                                    if (!ok) {
                                        const detail = client.getLastErrorMessage();
                                        addFileError(doc.uri, detail ? `自动合并保存失败：${detail}` : '自动合并保存失败：服务器未接受更改');
                                    } else {
                                        clearFileErrors(doc.uri);
                                    }
                                }
                                vscode.window.showInformationMessage(`${fileName}.meta.json 本地较新（时间相同），已自动覆盖服务器`);
                            }
                        } else if (!equal) {
                            // 额外判断：如果差异仅限于时间戳或参数 null/[]，直接使用服务器 meta 写回本地且不提示
                            try {
                                const left = this.metaManager.sanitizeMetaForDiff(local) || {};
                                const right = this.metaManager.sanitizeMetaForDiff(serverMeta) || {};
                                const leftRelax: any = { ...left };
                                const rightRelax: any = { ...right };
                                delete leftRelax.updateTime;
                                delete rightRelax.updateTime;
                                // 将空数组与缺失视为等价（仅针对 params）
                                if (Array.isArray(leftRelax.params) && leftRelax.params.length === 0 && typeof rightRelax.params === 'undefined') delete leftRelax.params;
                                if (Array.isArray(rightRelax.params) && rightRelax.params.length === 0 && typeof leftRelax.params === 'undefined') delete rightRelax.params;
                                const trivialEqual = this.metaManager.stableStringify(leftRelax) === this.metaManager.stableStringify(rightRelax);
                                if (trivialEqual) {
                                    const finalMeta = serverMeta!;
                                    const touched = this.metaManager.touchLocalMetaTime(finalMeta);
                                    await this.linkageManager.touchAndWriteMeta(mirrorRoot, touched);
                                    // 仅写本地并拦截一次保存，避免触发回推
                                    markSuppressNextSave(type, dir, fileName);
                                    return;
                                }
                            } catch(e) {console.warn(e)}
                            const choice = await vscode.window.showInformationMessage(
                                `检测到 ${fileName} 的元数据与服务器不一致，是否合并？`,
                                '打开合并', '以本地为准', '以服务器为准'
                            );
                            if (choice === '打开合并') {
                                await this.mergeManager.promptMergeForMeta(fileName, dir, mirrorRoot);
                            } else if (choice === '以本地为准' || choice === '以服务器为准') {
                                const useLocal = choice === '以本地为准';
                                const finalMeta = useLocal ? local : serverMeta;
                                if (finalMeta) {
                                    const touched = this.metaManager.touchLocalMetaTime(finalMeta);
                                    await this.linkageManager.touchAndWriteMeta(mirrorRoot, touched);
                                    if (!useLocal) {
                                        // 以服务器为准：仅写本地元数据，并设置一次性保存拦截；不推送到服务器
                                        markSuppressNextSave(type, dir, fileName);
                                        return;
                                    }
                                    // 合并到服务器：读取脚本并保存
                                    const msUri = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                                    let script = '';
                                    try { script = Buffer.from(await vscode.workspace.fs.readFile(msUri)).toString('utf8'); } catch(e) {console.warn(e)}
                                    if (fid && info) {
                                        const ok = await client.saveFile({
                                            ...info,
                                            name: finalMeta.name,
                                            path: finalMeta.path || info.path || '',
                                            method: finalMeta.method || info.method,
                                            requestMapping: finalMeta.requestMapping || info.requestMapping,
                                            description: finalMeta.description || info.description,
                                            // 类型专属字段：API
                                            ...(finalMeta.type === 'api' ? {
                                                params: finalMeta.params ?? (info as any).params,
                                                headers: finalMeta.headers ?? (info as any).headers,
                                                contentType: finalMeta.contentType ?? (info as any).contentType,
                                                timeout: finalMeta.timeout ?? (info as any).timeout,
                                            } : {}),
                                            // 类型专属字段：任务
                                            ...(finalMeta.type === 'task' ? {
                                                cron: finalMeta.cron ?? (info as any).cron,
                                                enabled: finalMeta.enabled ?? (info as any).enabled,
                                                executeOnStart: finalMeta.executeOnStart ?? (info as any).executeOnStart,
                                            } : {}),
                                            script
                                        } as any);
                                        if (!ok) {
                                            const detail = client.getLastErrorMessage();
                                            addFileError(doc.uri, detail ? `元数据保存失败：${detail}` : '元数据保存失败：服务器未接受更改');
                                        } else {
                                            clearFileErrors(doc.uri);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (e) {
                console.error(e)
                // 打开时合并失败不打断用户操作
            }
        }));

        // 跟踪到 active 映射，便于断开
        const key = mirrorRoot.fsPath;
        const old = this.activeMirrorDisposables.get(key);
        if (old && Array.isArray(old)) {
            try { old.forEach(d => d.dispose()); } catch(e) {console.warn(e)}
        }
        this.activeMirrorDisposables.set(key, disposables);
        return disposables;
    }

    // 提示创建镜像工作区（仅在登录成功后弹窗）
    public async promptCreateMirrorWorkspaceIfNeeded(client: MagicApiClient, serverConfig: MagicServerConfig): Promise<void> {
        // 仅当已成功登录或拥有有效令牌时才提示
        try { await client.ensureLogin(); } catch(e) {console.warn(e)}
        const authed = Object.keys(client.getAuthHeaders() || {}).length > 0;
        if (!authed) {
            return;
        }
        const folders = vscode.workspace.workspaceFolders;
        const currentFolder = folders && folders.length > 0 ? folders[0].uri : null;
        // 检查当前工作区是否已经是镜像工作区，如果是则不提示
        if (currentFolder && currentFolder.scheme === "file") {
            const isMirror = await this.isMirrorWorkspace(currentFolder);
            if (isMirror) {
                return; // 已经是镜像工作区，不需要提示
            }
        }
        if (!currentFolder || currentFolder.scheme !== "magic-api") {
            return
        }
        const action = await vscode.window.showInformationMessage(
            '是否创建对应的本地镜像工作区以进行本地维护？',
            '创建镜像工作区', '稍后'
        );
        if (action === '创建镜像工作区') {
            //获取当前用户home目录
            const defaultUri = vscode.Uri.file(os.homedir());
            const picked = await vscode.window.showOpenDialog({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, openLabel: '选择镜像工作区根目录', defaultUri });
 
            // 将选定目录作为镜像根写入连接信息并同步
            try {
                const folder = picked && picked.length > 0 ? picked[0] : undefined;
                if (!folder) return;
                // 在选定目录写入镜像元数据，并以该目录作为根（而不是 globalStorage）
                const baseMeta: MirrorMeta = {
                    createdAt: Date.now(),
                    url: serverConfig?.url,
                    token: serverConfig?.token,
                    username: serverConfig?.username,
                    password: serverConfig?.password,
                    lspPort: (serverConfig as any)?.lspPort,
                    debugPort: (serverConfig as any)?.debugPort,
                };
                await vscode.window.withProgress({
                    location: vscode.ProgressLocation.Notification,
                    title: '创建镜像工作区',
                    cancellable: false
                }, async (progress) => {
                    progress.report({ message: '写入镜像元数据' });
                    await this.metaManager.writeMirrorMeta(folder, baseMeta);
                    // 刷新写入本地补全用的工作台数据
                    progress.report({ message: '拉取工作台补全数据' });
                    await this.refreshWorkbenchCompletionData(client, folder, progress);
                    // 全量同步到选定目录
                    progress.report({ message: '同步服务器资源与脚本' });
                    await this.syncFromServer(client, folder, serverConfig, progress);
                    progress.report({ message: '打开镜像工作区（当前窗口）' });
                    const curFolders = vscode.workspace.workspaceFolders;
                    const nameFromCfg = serverConfig?.url ? `Mirror: ${new URL(serverConfig.url).host}` : 'Magic API Mirror';
                    const replaced = curFolders && curFolders.length > 0
                        ? vscode.workspace.updateWorkspaceFolders(0, curFolders.length, { uri: folder, name: nameFromCfg })
                        : false;
                    if (!replaced) {
                        // 无活动工作区时，回退到 openFolder（保持不新开窗口）
                        await vscode.commands.executeCommand('vscode.openFolder', folder, { forceNewWindow: false });
                    }

                    // 启动镜像监听，确保当前窗口即可进行推送/同步
                    try {
                        const disposables = this.startMirrorListeners(client, folder);
                        this.context.subscriptions.push(...disposables);
                    } catch(e) {console.warn(e)}
                });
            } catch (e) {
                vscode.window.showErrorMessage(`创建镜像工作区失败: ${String(e)}`);
            }
        }
    }

    // 加载镜像工作区时的差异比较与同步（改用 ZIP 生成器）
    public async compareAndSyncOnLoad(client: MagicApiClient, mirrorRoot: vscode.Uri): Promise<void> {
        console.log('[Mirror Debug] compareAndSyncOnLoad started');
        // 先进行对比，显示可取消的进度条
        let cancelled = false;
        const summary = { localOnly: 0, serverOnly: 0, changed: 0 };
        // 远端集合与映射（基于 ZIP 迭代项）
        const remotePaths = new Set<string>();
        const remoteItemMap = new Map<string, { type: MagicResourceType; groupPathSub: string; fileName: string; script: string; meta: MirrorFileMeta }>();

        console.log('[Mirror Debug] Starting comparison progress');
        await vscode.window.withProgress({
            location: vscode.ProgressLocation.Notification,
            title: '正在比较本地与远程镜像…',
            cancellable: true,
        }, async (progress, token) => {
            try {
                console.log('[Mirror Debug] Progress started, initializing');
                progress.report({ message: '初始化', increment: 5 });
                // 优先刷新一次工作台补全数据，供本地提示在 LSP 不可用时使用
                try { await this.refreshWorkbenchCompletionData(client, mirrorRoot, progress); } catch(e) {console.warn(e)}

                // 使用下载 ZIP 的生成器统一收集远端脚本与元数据
                for await (const item of this.syncManager.downloadAllResourcesYield(client, progress)) {
                    if (token.isCancellationRequested) { cancelled = true; return; }
                    if (item.kind !== 'script') continue;
                    const dir = item.groupPathSub ? `${item.type}/${item.groupPathSub}` : `${item.type}`;
                    const key = `${dir}/${item.fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`;
                    remotePaths.add(key);
                    remoteItemMap.set(key, { type: item.type, groupPathSub: item.groupPathSub, fileName: item.fileName, script: item.script, meta: item.meta });
                }

                if (token.isCancellationRequested) { cancelled = true; return; }
                progress.report({ message: '扫描本地文件', increment: 20 });

                // 扫描本地 .ms 文件
                const localItems = await this.fileUtils.listLocalMsFiles(mirrorRoot);
                const localPaths = new Set<string>();
                for (const item of localItems) {
                    if (token.isCancellationRequested) { cancelled = true; return; }
                    const key = `${item.dir}/${item.fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`;
                    localPaths.add(key);
                }

                // 汇总差异
                progress.report({ message: '分析差异', increment: 30 });
                for (const key of localPaths) {
                    if (!remotePaths.has(key)) summary.localOnly++;
                }
                for (const key of remotePaths) {
                    if (!localPaths.has(key)) summary.serverOnly++;
                }
                // 精确计算 changed：脚本或有效元数据差异（忽略本地字段）
                for (const key of remotePaths) {
                    if (!localPaths.has(key)) continue;
                    try {
                        const item = remoteItemMap.get(key);
                        if (!item) continue;
                        const dir = item.groupPathSub ? `${item.type}/${item.groupPathSub}` : `${item.type}`;
                        const fileName = item.fileName;
                        const type = item.type;
                        const groupPathSub = item.groupPathSub;

                        // 本地脚本与元数据
                        const msUri = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                        let localScript = '';
                        try { localScript = Buffer.from(await vscode.workspace.fs.readFile(msUri)).toString('utf8'); } catch(e) {console.warn(e)}
                        const localMeta = await this.metaManager.readLocalMeta(mirrorRoot, type, groupPathSub, fileName);

                        // 服务器脚本与元数据（来自 ZIP）
                        const serverScript = item.script;
                        const serverMeta: MirrorFileMeta | null = item.meta ? this.metaManager.toMirrorMetaFromServer({ ...item.meta, groupPath: dir }) : null;

                        const normalize = (s: string) => s.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
        const scriptsDifferent = normalize(localScript) !== normalize(serverScript);
        const { equal: metaEqual, left: localMetaCanon, right: serverMetaCanon } = this.metaManager.compareMeta(localMeta, serverMeta);
        const metaDifferent = !metaEqual;

                        // 调试日志：记录实际差异内容
                        if (scriptsDifferent || metaDifferent) {
                            console.log(`[Mirror Diff Debug] File: ${key}`);
                            if (scriptsDifferent) {
                                console.log(`[Mirror Diff Debug] Script difference detected`);
                                console.log(`[Mirror Diff Debug] Local script length: ${localScript.length}, Server script length: ${serverScript.length}`);
                                // 只记录前100个字符避免日志过长
                                console.log(`[Mirror Diff Debug] Local script preview: ${normalize(localScript).substring(0, 100)}...`);
                                console.log(`[Mirror Diff Debug] Server script preview: ${normalize(serverScript).substring(0, 100)}...`);
                            }
                            if (metaDifferent) {
                                console.log(`[Mirror Diff Debug] Meta difference detected`);
                                console.log(`[Mirror Diff Debug] Local meta:`, localMetaCanon);
                        console.log(`[Mirror Diff Debug] Server meta:`, serverMetaCanon);
                            }
                        }

                        if (scriptsDifferent || metaDifferent) summary.changed++;
                    } catch(e) {console.warn(e)}
                }

                progress.report({ message: '对比完成', increment: 45 });
            } catch (e) {
                // 若出现错误，交由后续统一提示；此处仅标记取消状态
                if ((e as any)?.message === 'Cancelled') cancelled = true;
            }
        });

        if (cancelled) {
            console.log('[Mirror Debug] Comparison cancelled');
            return;
        }
        console.log('[Mirror Debug] Comparison completed, summary:', summary);
        if (summary.localOnly === 0 && summary.serverOnly === 0 && summary.changed === 0) {
            console.log('[Mirror Debug] No differences found');
            vscode.window.showInformationMessage('未发现本地与服务器之间的差异');
            return;
        }

        // 根据差异动态展示同步选项
        const pickItems: vscode.QuickPickItem[] = [];
        const skipItem: vscode.QuickPickItem = { label: '跳过', description: '暂不进行同步' };
        if (summary.localOnly > 0 && summary.serverOnly === 0 && summary.changed === 0) {
            pickItems.push({ label: '同步本地到服务器', description: `本地新增 ${summary.localOnly} 项` });
            pickItems.push(skipItem);
        } else if (summary.serverOnly > 0 && summary.localOnly === 0 && summary.changed === 0) {
            pickItems.push({ label: '同步服务器到本地', description: `服务器新增 ${summary.serverOnly} 项` });
            pickItems.push(skipItem);
        } else {
            pickItems.push({ label: '双向同步', description: `本地新增 ${summary.localOnly}、服务器新增 ${summary.serverOnly}、变化 ${summary.changed}` });
            pickItems.push({ label: '同步本地到服务器', description: `仅推送本地，含新增 ${summary.localOnly}、变化 ${summary.changed}` });
            pickItems.push({ label: '同步服务器到本地', description: `仅拉取服务器，含新增 ${summary.serverOnly}、变化 ${summary.changed}` });
            pickItems.push(skipItem);
        }

        console.log('[Mirror Debug] Showing sync options:', pickItems.map(item => item.label));
        const choice = await vscode.window.showQuickPick(pickItems, { placeHolder: '发现差异，选择同步方式（可随时取消）' });
        console.log('[Mirror Debug] User choice:', choice?.label || 'cancelled');
        if (!choice || choice.label === '跳过') return;

        const twoWay = choice.label === '双向同步';
        const pushLocal = choice.label === '同步本地到服务器';
        const pullServer = choice.label === '同步服务器到本地';

        // 记录本地已处理的文件键，之后补充本地独有文件
        const seenLocalKeys = new Set<string>(remotePaths);

        // 执行所选策略，显示合并/同步进度
        console.log('[Mirror Debug] Starting sync execution with strategy:', { twoWay, pushLocal, pullServer });
        await vscode.window.withProgress({
            location: vscode.ProgressLocation.Notification,
            title: '正在执行所选同步策略…',
            cancellable: true,
        }, async (progress, token) => {
            console.log('[Mirror Debug] Sync progress started');
            const remoteCount = remoteItemMap.size;
            const incRemote = remoteCount > 0 ? (60 / remoteCount) : 0;

            // 基于远端 ZIP 项逐一做同步决策
            for (const [key, item] of remoteItemMap.entries()) {
                if (token.isCancellationRequested) return;
                const type = item.type;
                const groupPathSub = item.groupPathSub;
                const fileName = item.fileName;
                const dir = groupPathSub ? `${type}/${groupPathSub}` : `${type}`;
                progress.report({ message: `处理 ${dir}/${fileName}`, increment: incRemote });

                // 读取本地脚本与元数据
                const msUri = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                let localScript = '';
                try { localScript = Buffer.from(await vscode.workspace.fs.readFile(msUri)).toString('utf8'); } catch(e) {console.warn(e)}
                const localMeta = await this.metaManager.readLocalMeta(mirrorRoot, type, groupPathSub, fileName);

                // 服务器脚本与元数据（来自 ZIP）
                const serverScript = item.script;
                const serverMeta: MirrorFileMeta | null = item.meta ? { ...item.meta, groupPath: dir } : null;

                const localTime = this.metaManager.getLocalUpdateTime(localMeta);
                const serverTime = serverMeta?.updateTime ?? 0;

                const normalizeScript = (s: string) => s.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
                const scriptsDifferent = normalizeScript(localScript) !== normalizeScript(serverScript);
                const metaDifferent = JSON.stringify(this.metaManager.sanitizeMetaForDiff(localMeta) || {}) !== JSON.stringify(this.metaManager.sanitizeMetaForDiff(serverMeta) || {});

                // 决策：twoWay / pushLocal / pullServer
                let decideUseLocal = false;
                let decideUseServer = false;
                if (twoWay) {
                    if (localTime > serverTime) decideUseLocal = true;
                    else if (serverTime > localTime) decideUseServer = true;
                    else {
                        if (scriptsDifferent) {
                            await this.mergeManager.promptMergeForFile(fileName, dir, mirrorRoot);
                            continue;
                        }
                        if (metaDifferent) {
                            await this.mergeManager.promptMergeForMeta(fileName, dir, mirrorRoot);
                            continue;
                        }
                        continue;
                    }
                } else if (pushLocal) {
                    if (localTime >= serverTime) decideUseLocal = true;
                    else if (scriptsDifferent || metaDifferent) {
                        if (scriptsDifferent) await this.mergeManager.promptMergeForFile(fileName, dir, mirrorRoot);
                        if (metaDifferent) await this.mergeManager.promptMergeForMeta(fileName, dir, mirrorRoot);
                        continue;
                    } else {
                        continue;
                    }
                } else if (pullServer) {
                    if (serverTime >= localTime) decideUseServer = true;
                    else if (scriptsDifferent || metaDifferent) {
                        if (scriptsDifferent) await this.mergeManager.promptMergeForFile(fileName, dir, mirrorRoot);
                        if (metaDifferent) await this.mergeManager.promptMergeForMeta(fileName, dir, mirrorRoot);
                        continue;
                    } else {
                        continue;
                    }
                }

                // 执行合并：以本地为准 -> 保存到服务器；以服务器为准 -> 落地到本地
                if (decideUseLocal) {
                    const metaToUse: MirrorFileMeta = {
                        ...(serverMeta || {}),
                        ...(localMeta || {}),
                        name: (localMeta?.name || serverMeta?.name || fileName)!,
                        type,
                        groupPath: dir,
                    } as MirrorFileMeta;

                    // 若缺少 id，尝试通过路径解析以进行更新；否则走创建流程
                    let serverId = serverMeta?.id;
                    if (!serverId) {
                        try { serverId = await client.resolveFileIdByPath(key) || undefined; } catch(e) {console.warn(e)}
                    }
                    if (serverId) {
                        const ok = await client.saveFile({
                            id: serverId!,
                            name: metaToUse.name,
                            path: String(metaToUse.path || ''),
                            script: localScript,
                            groupId: String(metaToUse.groupId || ''),
                            groupPath: dir,
                            type,
                            method: metaToUse.method,
                            requestMapping: metaToUse.requestMapping,
                            description: metaToUse.description,
                            ...(type === 'api' ? {
                                params: metaToUse.params,
                                headers: metaToUse.headers,
                                contentType: metaToUse.contentType,
                                timeout: metaToUse.timeout,
                            } : {}),
                            ...(type === 'task' ? {
                                cron: metaToUse.cron,
                                enabled: metaToUse.enabled,
                                executeOnStart: metaToUse.executeOnStart,
                            } : {}),
                        } as any);
                        if (!ok) {
                            const msUri2 = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                            const detail = client.getLastErrorMessage();
                            addFileError(msUri2, detail ? `保存到服务器失败：${detail}` : '保存到服务器失败');
                        } else {
                            const msUri2 = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                            clearFileErrors(msUri2);
                        }
                    } else {
                        await this.syncManager.ensureGroups(client, type, groupPathSub);
                        const newId = await client.createFile({
                            name: metaToUse.name,
                            script: localScript,
                            type,
                            groupPath: dir,
                            method: metaToUse.method,
                            requestMapping: metaToUse.requestMapping,
                            description: metaToUse.description,
                            ...(type === 'api' ? {
                                params: metaToUse.params,
                                headers: metaToUse.headers,
                                contentType: metaToUse.contentType,
                                timeout: metaToUse.timeout,
                            } : {}),
                            ...(type === 'task' ? {
                                cron: metaToUse.cron,
                                enabled: metaToUse.enabled,
                                executeOnStart: metaToUse.executeOnStart,
                            } : {}),
                        });
                        const msUri2 = vscode.Uri.joinPath(mirrorRoot, ...dir.split('/'), `${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`);
                        if (!newId) {
                            const detail = client.getLastErrorMessage();
                            addFileError(msUri2, detail ? `同步失败：${detail}` : '同步失败');
                        } else {
                            clearFileErrors(msUri2);
                        }
                    }
                } else if (decideUseServer && serverMeta) {
                    await this.metaManager.writeLocalScriptAndUpdateMeta(
                        mirrorRoot,
                        type,
                        groupPathSub,
                        fileName,
                        serverScript,
                        serverMeta
                    );
                    // 设置一次性保存拦截，避免本地写入触发回推
                    {
                        const supSet = this.suppressNextSavePushKeys.get(mirrorRoot.fsPath) ?? new Set<string>();
                        this.suppressNextSavePushKeys.set(mirrorRoot.fsPath, supSet);
                        const msKey = `${dir}/${fileName}${MIRROR_FILE_CONSTANTS.SCRIPT_FILE_EXTENSION}`;
                        const metaKey = `${dir}/.${fileName}${MIRROR_FILE_CONSTANTS.META_FILE_EXTENSION}`;
                        supSet.add(msKey);
                        supSet.add(metaKey);
                    }
                }
            }

            // 扫描本地独有的 .ms 文件（服务器不存在）并按策略处理
            progress.report({ message: '处理本地独有文件…' });
            const localOnly = await this.fileUtils.listLocalMsFiles(mirrorRoot);
            const incLocal = localOnly.length > 0 ? (40 / localOnly.length) : 0;
            for (const item of localOnly) {
                if (token.isCancellationRequested) return;
                const fileKey = `${item.dir}/${item.fileName}${item.type === 'datasource' ? '.json' : '.ms'}`;
                if (seenLocalKeys.has(fileKey)) continue; // 已在上面处理（远端存在）
                progress.report({ message: `处理 ${item.dir}/${item.fileName}`, increment: incLocal });
                // 读取本地脚本与元数据
                const msUri = vscode.Uri.joinPath(mirrorRoot, ...item.dir.split('/'), `${item.fileName}${item.type === 'datasource' ? '.json' : '.ms'}`);
                let localScript = '';
                try { localScript = Buffer.from(await vscode.workspace.fs.readFile(msUri)).toString('utf8'); } catch(e) {console.warn(e)}
                const localMeta = await this.metaManager.readLocalMeta(mirrorRoot, item.type, item.groupPathSub, item.fileName);

                if (pushLocal || twoWay) {
                    // 推送到服务器
                    await this.syncManager.ensureGroups(client, item.type, item.groupPathSub);
                    const id = await client.createFile({
                        name: item.fileName,
                        script: localScript,
                        type: item.type,
                        groupPath: item.dir,
                        method: localMeta?.method,
                        requestMapping: localMeta?.requestMapping,
                        description: localMeta?.description,
                        ...(item.type === 'api' ? {
                            params: localMeta?.params,
                            headers: localMeta?.headers,
                            contentType: localMeta?.contentType,
                            timeout: localMeta?.timeout,
                        } : {}),
                        ...(item.type === 'task' ? {
                            cron: localMeta?.cron,
                            enabled: localMeta?.enabled,
                            executeOnStart: localMeta?.executeOnStart,
                        } : {}),
                    });
                    if (!id) {
                        const detail = client.getLastErrorMessage();
                        addFileError(msUri, detail ? `同步失败：${detail}` : '同步失败');
                    } else {
                        clearFileErrors(msUri);
                    }
                } else if (pullServer) {
                    // 保留本地，不操作
                    continue;
                }
            }
        });
    }

    // 根据任意文件 URI 查找其所在镜像工作区根目录
    public async findMirrorRootForUri(uri: vscode.Uri): Promise<vscode.Uri | null> {
        return this.fileUtils.findMirrorRootForUri(uri);
    }

    // 扫描当前工作区，查找所有镜像根目录（支持镜像文件夹在工作区的子目录的场景）
    public async findAllMirrorRootsInWorkspace(): Promise<vscode.Uri[]> {
        return this.fileUtils.findAllMirrorRootsInWorkspace();
    }

    // 根据连接信息在工作区内查找对应的镜像根目录
    public async findMirrorRootByConnection(conn: { url?: string; token?: string; username?: string; password?: string; lspPort?: number; debugPort?: number; }): Promise<vscode.Uri | null> {
        return this.workspaceUtils.findMirrorRootByConnection(await this.findAllMirrorRootsInWorkspace(), conn);
    }

    // 公开方法：连接镜像根（启动监听并进行一次比较同步）
    public async connectMirrorRoot(client: MagicApiClient, root: vscode.Uri): Promise<void> {
        console.log('[Mirror Debug] connectMirrorRoot called for:', root.fsPath);
        const disposables = this.startMirrorListeners(client, root);
        this.context.subscriptions.push(...disposables);
        console.log('[Mirror Debug] About to call compareAndSyncOnLoad');
        try { await this.compareAndSyncOnLoad(client, root); } catch(e) {console.warn('[Mirror Debug] compareAndSyncOnLoad error:', e)}
    }

    // 公开方法：断开镜像根（释放监听）
    public disconnectMirrorRoot(root: vscode.Uri): void {
        const key = root.fsPath;
        const arr = this.activeMirrorDisposables.get(key);
        if (arr && Array.isArray(arr)) {
            try { arr.forEach(d => d.dispose()); } catch(e) {console.warn(e)}
        }
        this.activeMirrorDisposables.delete(key);
    }

    // 查询镜像根是否已连接（是否存在活动监听器）
    public isMirrorRootConnected(root: vscode.Uri): boolean {
        const arr = this.activeMirrorDisposables.get(root.fsPath);
        return !!(arr && arr.length > 0);
    }

    // 解析镜像工作区内文件的相对信息
    public parseMirrorFile(mirrorRoot: vscode.Uri, fileUri: vscode.Uri): { type: MagicResourceType | null; groupPathSub: string; fileName: string | null; typedPath: string | null } {
        return this.fileUtils.parseMirrorFile(mirrorRoot, fileUri);
    }
}